/**
 * 
 */
package com.orcwar.engine;

import java.util.ArrayList;
import java.util.List;

import android.graphics.Color;
import android.graphics.LightingColorFilter;
import android.graphics.Paint;
import android.graphics.RectF;

import com.orcwar.engine.items.listeners.IOnAttackListener;
import com.orcwar.engine.items.listeners.IOnCheckMandatoryTileListener;
import com.orcwar.engine.items.listeners.IOnCheckTileAttackableListener;
import com.orcwar.engine.items.listeners.IOnDieListener;
import com.orcwar.engine.items.listeners.IOnMoveListener;
import com.orcwar.engine.items.listeners.IOnMoveSimulationListener;
import com.orcwar.engine.tile.OWAttackTile;
import com.orcwar.enums.DirectionEnum;
import com.orcwar.enums.OWAttackTileTypeEnum;
import com.orcwar.enums.OWTileType;
import com.orcwar.enums.SpriteEnum;

/**
 * @author Callil
 * 
 */
public abstract class OWUnit {

	/** The unit id. */
	private int id;
	
	private OWTile tile;
	
	private OWPlayer player;

	/** The unit's sprite. */
	private SpriteEnum sprite;

	/** The current position of the unit. */
	private RectF position;

	/** The orientation of the unit. */
	private DirectionEnum orientation = DirectionEnum.UP;

	/** Whether this unit is an enemy one. */
	private boolean enemy;

	/**
	 * How many moves per turn can the unit make.
	 */
	private int maxMovePoints;

	/**
	 * The number of remaining move points for the current turn.
	 */
	private int movePoints;

	/**
	 * The number of movePoints unused because the unit attacked without using
	 * every movePoint.
	 */
	private int movePointsUnused;

	/**
	 * How many attacks per turn can the unit make.
	 */
	private int maxAttackPoints;
	/**
	 * How many attacks left this turn.
	 */
	private int attackPoints;

	private List<IOnAttackListener> onAttackListeners = new ArrayList<IOnAttackListener>();
	private List<IOnMoveListener> onMoveListeners = new ArrayList<IOnMoveListener>();
	private List<IOnDieListener> onDieListeners = new ArrayList<IOnDieListener>();
	private List<IOnMoveSimulationListener> onMoveSimulationListeners = new ArrayList<IOnMoveSimulationListener>();
	private List<IOnCheckMandatoryTileListener> onCheckMandatoryTileListeners = new ArrayList<IOnCheckMandatoryTileListener>();
	private List<IOnCheckTileAttackableListener> onCheckTileAttackableListeners = new ArrayList<IOnCheckTileAttackableListener>();

	private boolean turnEnded;

	private List<OWItem> items;

	// Draw attributes
	private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
	private LightingColorFilter endTurnMask = new LightingColorFilter(
			Color.rgb(0, 0, 0), Color.rgb(0, 0, 0));

	/**
	 * Protected constructor
	 * 
	 * @param id
	 *            the id
	 * @param sprite
	 *            the sprite
	 */
	protected OWUnit(int id, OWPlayer player, SpriteEnum sprite) {
		this.id = id;
		this.player = player;
		this.sprite = sprite;
	}

	public void startNewTurn() {
		turnEnded = false;
		movePoints = maxMovePoints;
		movePointsUnused = 0;
		attackPoints = maxAttackPoints;
	}
	
	
	//************************//
	// ATTACK RELATED METHODS //
	/**
	 * This method returns for a given unit all the tiles that need to
	 * be available (no unit, no wall) so that the unit can perform any
	 * attack. For example, for the FLEATEUR, it will return all tiles
	 * surrounding him in 8-connexity.
	 * @return a list of tiles
	 */
	public abstract OWAttackTile[] getGlobalMandatoryAvailableTiles(OWMap map, OWTile fromTile);
	
	/**
	 * CHeck whether a mandatory tile is not blocked.
	 * @param tile The tile
	 * @return true if the mandatory tile is available
	 */
	public boolean isMandatoryTileAvailable(OWAttackTile tile) {
		boolean mandatoryUnitValid = !(tile.getType().contains(OWAttackTileTypeEnum.MANDATORY_NOT_UNIT)
				&& tile.getCurrentUnit() != null);
		boolean mandatoryWallValid = !(tile.getType().contains(OWAttackTileTypeEnum.MANDATORY_NOT_WALL)
						&& tile.getTileType() == OWTileType.WALL);
		
		boolean avail = mandatoryUnitValid && mandatoryWallValid;
		
		//TODO: might change this. For now, if at least one item says it's ok, then return true
		//But what to do if an item says it's not possible ??
		for (IOnCheckMandatoryTileListener l : onCheckMandatoryTileListeners) {
			if (l.onCheckMandatoryTile(tile)) {
				avail = true;
				break;
			}
		}
		
		return avail;
	}
	
	/**
	 * Returns <code>true</code> if the unit situated in the tile
	 * <code>src</code> has enough range to hit a target in the tile
	 * <code>target</code>.
	 * 
	 * @return <code>true</code> if range is enough.
	 */
	public abstract boolean isRangeOk(int srcX, int srcY, int targetX,
			int targetY);
	
	
	
	public boolean canAttackTile(OWTile tile) {
		//Basically, all tiles are attackable. However, a negative item
		//could prevent a tile from being attacked.

		for (IOnCheckTileAttackableListener l : onCheckTileAttackableListeners) {
			if (!l.onCheckTileAttackable(tile)) return false;
		}
		
		return true;
	}
	
	/**
	 * Return the tiles where the unit can attack.
	 * @return A list of list of tiles. In a sublist, the tiles are ordered so that
	 * if the unit cannot attack the first tile in it, it's useless to test the next
	 * tiles of the list
	 */
	public abstract OWAttackTile[][] getAttackTiles(OWMap map, OWTile fromTile); 

	/**
	 * Use 1 move point. If the unit had only one move point remaining, it's turn
	 * ends.
	 */
	public void useMovePoint() {
		movePoints -= 1;
		// Safe check
		if (movePoints < 0)
			movePoints = 0;
	}

	/**
	 * Fires an event to listeners and ALSO finishes the turn of the unit.
	 * 
	 * @param targetId
	 *            The id of the tile attacked.
	 */
	public boolean onAttack(int targetId) {
		for (IOnAttackListener l : onAttackListeners) {
			if (!l.onAttack(targetId)) return false;
		}
		return true;
	}

	/**
	 * Use one attack point.
	 */
	public void useAttackPoint() {
		attackPoints--;
		if (attackPoints < 0)
			attackPoints = 0;
		
		// if there were movePoints left, remove them and store them in case the
		// unit can still move after attack (with an item). Default behavior is
		// no move after an attack.
		if (movePoints > 0) {
			movePointsUnused = movePoints;
			movePoints = 0;
		}
	}
	
	// ATTACK RELATED METHODS //
	//************************//

	
	
	//**********************//
	// 		LISTENERS 		//
	/**
	 * Call the onMoveListener of this unit, ie. check if the items impact the
	 * current movement. It also compute the orientation of the unit to face the
	 * tile it's going to.
	 * 
	 * @param source
	 *            the source tile
	 * @param targer
	 *            the target tile
	 * @return true if the movement is allowed, false if something cancelled the
	 *         movement (f.e an item that prevents going on this tile).
	 */
	public boolean onMove(OWTile source, OWTile target) {
		orientation = source.directionToTile(target);
		boolean result = true;
		for (IOnMoveListener l : onMoveListeners) {
			if (!l.onMove(source, target))
				result = false;
		}

		return result;
	}

	public void onDie() {
		for (IOnDieListener l : onDieListeners) {
			l.onDie(id);
		}
		// make other actions upon dying? TODO
	}

	public void onMoveSimulation(OWTile source, OWTile target) {
		for (IOnMoveSimulationListener l : onMoveSimulationListeners) {
			l.onMove(source, target);
		}
		// Show what is going to happen TODO
	}
	
	// 		LISTENERS 		//
	//**********************//
	

	/* (non-Javadoc)
	 * @see java.lang.Object#hashCode()
	 */
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + id;
		return result;
	}

	/* (non-Javadoc)
	 * @see java.lang.Object#equals(java.lang.Object)
	 */
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		OWUnit other = (OWUnit) obj;
		if (id != other.id)
			return false;
		return true;
	}

	public void addOnAttackListener(IOnAttackListener onAttackListener) {
		onAttackListeners.add(onAttackListener);
	}

	public void removeOnAttackListener(IOnAttackListener onAttackListener) {
		onAttackListeners.remove(onAttackListener);
	}

	public void addOnMoveListener(IOnMoveListener onMoveListener) {
		onMoveListeners.add(onMoveListener);
	}

	public void removeOnMoveListener(IOnMoveListener onMoveListener) {
		onMoveListeners.remove(onMoveListener);
	}

	public void addOnDieListener(IOnDieListener onDieListener) {
		onDieListeners.add(onDieListener);
	}

	public void removeOnDieListener(IOnDieListener onDieListener) {
		onDieListeners.remove(onDieListener);
	}

	public void addOnMoveSimulationListener(
			IOnMoveSimulationListener onMoveSimulationListener) {
		onMoveSimulationListeners.add(onMoveSimulationListener);
	}

	public void removeOnMoveSimulationListener(
			IOnMoveSimulationListener onMoveSimulationListener) {
		onMoveSimulationListeners.remove(onMoveSimulationListener);
	}

	public int getMovePoints() {
		return movePoints;
	}

	public void setMovePoints(int movePoints) {
		this.movePoints = movePoints;
	}

	public int getAttackPoints() {
		return attackPoints;
	}

	public void setAttackPoints(int attackPoints) {
		this.attackPoints = attackPoints;
	}

	/**
	 * @return the id
	 */
	public int getId() {
		return id;
	}

	/**
	 * @param id
	 *            the id to set
	 */
	public void setId(int id) {
		this.id = id;
	}

	/**
	 * @return the enemy
	 */
	public boolean isEnemy() {
		return enemy;
	}

	/**
	 * @param enemy
	 *            the enemy to set
	 */
	public void setEnemy(boolean enemy) {
		this.enemy = enemy;
	}

	public boolean isTurnEnded() {
		return turnEnded;
	}

	public void setTurnEnded(boolean turnEnded) {
		this.turnEnded = turnEnded;
	}

	/**
	 * @return the sprite
	 */
	public SpriteEnum getSprite() {
		return sprite;
	}

	/**
	 * @param sprite
	 *            the sprite to set
	 */
	public void setSprite(SpriteEnum sprite) {
		this.sprite = sprite;
	}

	/**
	 * @return the maxMovePoints
	 */
	public int getMaxMovePoints() {
		return maxMovePoints;
	}

	/**
	 * @param maxMovePoints
	 *            the maxMovePoints to set
	 */
	public void setMaxMovePoints(int maxMovePoints) {
		this.maxMovePoints = maxMovePoints;
	}

	/**
	 * @return the orientation
	 */
	public DirectionEnum getOrientation() {
		return orientation;
	}

	/**
	 * @param orientation
	 *            the orientation to set
	 */
	public void setOrientation(DirectionEnum orientation) {
		this.orientation = orientation;
	}

	public int getMovePointsUnused() {
		return movePointsUnused;
	}

	public void setMovePointsUnused(int movePointsUnused) {
		this.movePointsUnused = movePointsUnused;
	}

	/**
	 * @return the position
	 */
	public RectF getPosition() {
		return position;
	}

	/**
	 * @param position
	 *            the position to set
	 */
	public void setPosition(RectF position) {
		this.position = position;
	}

	public List<OWItem> getItems() {
		return items;
	}

	public void setItems(List<OWItem> items) {
		this.items = items;
	}

	public int getMaxAttackPoints() {
		return maxAttackPoints;
	}

	public void setMaxAttackPoints(int maxAttackPoints) {
		this.maxAttackPoints = maxAttackPoints;
	}

	@Override
	public String toString() {
		return "[" + this.getClass().getName() + ":items="
				+ (items == null ? "null" : items.toString()) + "]";
	}

	/**
	 * @return the tile
	 */
	public OWTile getTile() {
		return tile;
	}

	/**
	 * @param tile the tile to set
	 */
	public void setTile(OWTile tile) {
		this.tile = tile;
	}

	public OWPlayer getPlayer() {
		return player;
	}

	/**
	 * @return the onCheckMandatoryTileListeners
	 */
	public List<IOnCheckMandatoryTileListener> getOnCheckMandatoryTileListeners() {
		return onCheckMandatoryTileListeners;
	}

	/**
	 * @param onCheckMandatoryTileListeners the onCheckMandatoryTileListeners to set
	 */
	public void setOnCheckMandatoryTileListeners(
			List<IOnCheckMandatoryTileListener> onCheckMandatoryTileListeners) {
		this.onCheckMandatoryTileListeners = onCheckMandatoryTileListeners;
	}

	/**
	 * @return the onCheckTileAttackableListeners
	 */
	public List<IOnCheckTileAttackableListener> getOnCheckTileAttackableListeners() {
		return onCheckTileAttackableListeners;
	}

	/**
	 * @param onCheckTileAttackableListeners the onCheckTileAttackableListeners to set
	 */
	public void setOnCheckTileAttackableListeners(
			List<IOnCheckTileAttackableListener> onCheckTileAttackableListeners) {
		this.onCheckTileAttackableListeners = onCheckTileAttackableListeners;
	}
}
